package plochki;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Scanner;

public class BaseWorker {
	
	//All the input figures
	ArrayList<Figura> figuri;
	
	//The game field as an int matrix
	int[][] banqta;
	
	//The name of the input file,
	String inputFile;
	
	//Strings with console input for each Figure, if the input is from the console
	String[] consoleInputFigures = null;
	
	//The moves made during the work
	public int movesMade;

	//The time required for solving the task
	public long time;
	
	//The name of the algorithm
	public String name;
	
	//Base constructor
	public BaseWorker(String inputFile)
	{
		this.inputFile = inputFile;
		
		this.figuri = new ArrayList<Figura>();
		
		this.banqta = new int[500][500];
		
		this.movesMade = 0;
		
		this.time = 0;
		
		this.name = "BaiIvan";
	}
	
	//Base constructor
	public BaseWorker(String[] consoleInputFigures)
	{
		this.consoleInputFigures = consoleInputFigures;
		
		this.figuri = new ArrayList<Figura>();
		
		this.banqta = new int[500][500];
		
		this.movesMade = 0;
		
		this.time = 0;
		
		this.name = "BaiIvan";
	}

	//Main Solving method
	public void deistvai() throws IOException {
		
		long startTime = System.currentTimeMillis();
		
		if (this.consoleInputFigures != null) {
			this.populateFiguriFromConsoleLines();
		}
		else {
			this.populateFiguriFromFile();
		}
		
		//Create Field
		this.initializeField();

		//Move all the overlapping figures
		//Simpler Solution
		this.nalepiPlochki();

		//Measure the time required for the algorithm to finish
		this.time = System.currentTimeMillis() - startTime;
		
		//Test the result
		//this.printFigures();
		
		//Writer the final field to a File
		//this.printFieldToFile();
	
		//Print the work done by the Worker
		//this.printWorkDone();
	}

	//Move a Figure to a new location, (posX, posY) with distance from the initial place
	protected void moveFigure(Figura fig, int posX, int posY,
			int distance) {
		
		//Print every move
		//System.out.println(fig.figuraType + " (" + fig.posX + "," + fig.posY
		//		+ ")->(" + posX + "," + posY + ")");
		
		//Place the Figure at its best new position
		fig.posX = posX;
		fig.posY = posY;
		
		//Update the work done
		this.movesMade += distance;
		
		//Update the main Field
		this.addToField(fig);
		
		//When a field is moved, its stuck level is reset
		fig.stuckLevel = 0;
	}
	
	//Find the closes free space for a Figure
	//Go over in a rectangular path around the current position until a free spot is available
	protected void findBestPlaceForFigure(Figura fig) {
		//the current radius
		int radius;
		//current step for the traversal in a certain radius
		int step;
		
		//calculate borders of the search
		//the end of the field
		int leftBound = fig.leftOffset;
		int rightBound = this.banqta[0].length - 1 - fig.rightOffset;
		int upBound = fig.upOffset;
		int downBound = this.banqta[0].length - 1 - fig.downOffset;
		
		int tempX;
		int tempY;
		
		//Search for a free spot around the current position
		//Traverse in the form of rotated rectangles
		//Start from the radius that was last traversed
		//New place is always possible, no limitations on the searching radius
		for(radius = fig.bestMoveDistance; ; ++ radius) {
			for(step = 0; step < radius; ++step) {
				//Check up right side of the rotated rectangle
				tempX = fig.posX - radius + step;
				tempY = fig.posY + step;
				if(tempX >= upBound && tempY <= rightBound) {
					if (this.checkForFreeSpot(fig, tempX, tempY)) {
						//When the closes free spot is found for a figure
						//Decide what to do next based on the algorithm
						this.decideWhatToDo(fig, tempX, tempY, radius);
						
						return;
					}
				}
				
				//Check down right side of the rotated rectangle
				tempX = fig.posX + step;
				tempY = fig.posY + radius - step;
				if(tempX <= downBound && tempY <= rightBound) {
					if (this.checkForFreeSpot(fig, tempX, tempY)) {
						//Decide what to do next based on the algorithm
						this.decideWhatToDo(fig, tempX, tempY, radius);
						
						return;
					}
				}
				
				//Check down left side of the rotated rectangle
				tempX = fig.posX + radius - step;
				tempY = fig.posY - step;
				if(tempX <= downBound && tempY >= leftBound) {
					if (this.checkForFreeSpot(fig, tempX, tempY)) {
						//Decide what to do next based on the algorithm
						this.decideWhatToDo(fig, tempX, tempY, radius);
						
						return;
					}
				}
				
				//Check up left side of the rotated rectangle
				tempX = fig.posX - step;
				tempY = fig.posY - radius + step;
				if(tempX >= upBound && tempY >= leftBound) {
					if (this.checkForFreeSpot(fig, tempX, tempY)) {
						//Decide what to do next based on the algorithm
						this.decideWhatToDo(fig, tempX, tempY, radius);
						
						return;
					}
				}
			}
		}
	}
	
	//WHat to do when a better position is found for a stuck figure
	//Different approaches for different algorithms
	protected void decideWhatToDo(Figura fig, int posX, int posY, int distance) {
		//move the Figure
		this.moveFigure(fig, posX, posY, distance);
	}

	//Check if the figure (fig) fits in a space with center (posA, posB)
	private boolean checkForFreeSpot(Figura fig, int posA, int posB) {
		//Logging
		//System.err.println("check " + fig.figuraType + " at (" + posA + "," + posB + ")");
		switch(fig.figuraType) {
			case"ninetile":
			{
				if ((banqta[posA][posB]>0) 
					||(banqta[posA][posB-1]>0) 
					||(banqta[posA-1][posB-1]>0) 
					||(banqta[posA-1][posB]>0) 
					||(banqta[posA-1][posB+1]>0) 
					||(banqta[posA][posB+1]>0) 
					||(banqta[posA+1][posB+1]>0) 
					||(banqta[posA+1][posB]>0) 
					||(banqta[posA+1][posB-1]>0))
					return false;
				break;
			}
			case"plus":
			{
				if ((banqta[posA][posB]>0)
					||(banqta[posA][posB-1]>0)
					||(banqta[posA][posB+1]>0)
					||(banqta[posA-1][posB]>0)
					||(banqta[posA+1][posB]>0))
					return false;
				break;
			}
			case"hline":
			{
				if ((banqta[posA][posB]>0)
					||(banqta[posA][posB-1]>0)
					||(banqta[posA][posB+1]>0))
					return false;
				break;
			}
			case"vline":
			{
				if ((banqta[posA][posB]>0)
					||(banqta[posA-1][posB]>0)
					||(banqta[posA+1][posB]>0))
					return false;
				break;
			}
			case"angle-ur":
			{
				if ((banqta[posA][posB]>0)
					||(banqta[posA-1][posB]>0)
					||(banqta[posA][posB+1]>0))
					return false;
				break;
			}
			case"angle-dr":
			{
				if ((banqta[posA][posB]>0)
					||(banqta[posA+1][posB]>0)
					||(banqta[posA][posB+1]>0))
					return false;
				break;
			}
			case"angle-ul":
			{
				if ((banqta[posA][posB]>0)
					||(banqta[posA-1][posB]>0)
					||(banqta[posA][posB-1]>0))
					return false;
				break;
			}
			case"angle-dl":
			{
				if ((banqta[posA][posB]>0)
					||(banqta[posA+1][posB]>0)
					||(banqta[posA][posB-1]>0))
					return false;
				break;
			}
		}
		
		return true;
	}

	//Remove a Figure from the field
	protected void otlepiPlochka(Figura fig) {
		
		switch(fig.figuraType) {
			case"ninetile":
			{
				if (banqta[fig.posX][fig.posY]>0) --banqta[fig.posX][fig.posY];
				if (banqta[fig.posX][fig.posY-1]>0) --banqta[fig.posX][fig.posY-1];
				if (banqta[fig.posX-1][fig.posY-1]>0) --banqta[fig.posX-1][fig.posY-1];
				if (banqta[fig.posX-1][fig.posY]>0) --banqta[fig.posX-1][fig.posY];
				if (banqta[fig.posX-1][fig.posY+1]>0) --banqta[fig.posX-1][fig.posY+1];
				if (banqta[fig.posX][fig.posY+1]>0) --banqta[fig.posX][fig.posY+1];
				if (banqta[fig.posX+1][fig.posY+1]>0) --banqta[fig.posX+1][fig.posY+1];
				if (banqta[fig.posX+1][fig.posY]>0) --banqta[fig.posX+1][fig.posY];
				if (banqta[fig.posX+1][fig.posY-1]>0) --banqta[fig.posX+1][fig.posY-1];
				break;
			}
			case"plus":
			{
				if (banqta[fig.posX][fig.posY]>0) --banqta[fig.posX][fig.posY];
				if (banqta[fig.posX][fig.posY-1]>0) --banqta[fig.posX][fig.posY-1];
				if (banqta[fig.posX][fig.posY+1]>0) --banqta[fig.posX][fig.posY+1];
				if (banqta[fig.posX-1][fig.posY]>0) --banqta[fig.posX-1][fig.posY];
				if (banqta[fig.posX+1][fig.posY]>0) --banqta[fig.posX+1][fig.posY];
				break;
			}
			case"hline":
			{
				if (banqta[fig.posX][fig.posY]>0) --banqta[fig.posX][fig.posY];
				if (banqta[fig.posX][fig.posY-1]>0) --banqta[fig.posX][fig.posY-1];
				if (banqta[fig.posX][fig.posY+1]>0) --banqta[fig.posX][fig.posY+1];
				break;
			}
			case"vline":
			{
				if (banqta[fig.posX][fig.posY]>0) --banqta[fig.posX][fig.posY];
				if (banqta[fig.posX-1][fig.posY]>0) --banqta[fig.posX-1][fig.posY];
				if (banqta[fig.posX+1][fig.posY]>0) --banqta[fig.posX+1][fig.posY];
				break;
			}
			case"angle-ur":
			{
				if (banqta[fig.posX][fig.posY]>0) --banqta[fig.posX][fig.posY];
				if (banqta[fig.posX-1][fig.posY]>0) --banqta[fig.posX-1][fig.posY];
				if (banqta[fig.posX][fig.posY+1]>0) --banqta[fig.posX][fig.posY+1];
				break;
			}
			case"angle-dr":
			{
				if (banqta[fig.posX][fig.posY]>0) --banqta[fig.posX][fig.posY];
				if (banqta[fig.posX+1][fig.posY]>0) --banqta[fig.posX+1][fig.posY];
				if (banqta[fig.posX][fig.posY+1]>0) --banqta[fig.posX][fig.posY+1];
				break;
			}
			case"angle-ul":
			{
				if (banqta[fig.posX][fig.posY]>0) --banqta[fig.posX][fig.posY];
				if (banqta[fig.posX-1][fig.posY]>0) --banqta[fig.posX-1][fig.posY];
				if (banqta[fig.posX][fig.posY-1]>0) --banqta[fig.posX][fig.posY-1];
				break;
			}
			case"angle-dl":
			{
				if (banqta[fig.posX][fig.posY]>0) --banqta[fig.posX][fig.posY];
				if (banqta[fig.posX+1][fig.posY]>0) --banqta[fig.posX+1][fig.posY];
				if (banqta[fig.posX][fig.posY-1]>0) --banqta[fig.posX][fig.posY-1];
				break;
			}
		}
	}

	//Gets the input from the console in the appropriate format
	//Input read from a file with a name fileName
	protected void populateFiguriFromFile() throws FileNotFoundException {
		Scanner reader;
		if (this.inputFile.equals("")) {
			reader = new Scanner(System.in);
		} else {
			reader = new Scanner(new FileReader(new File(this.inputFile)));
		}
		
		int n = Integer.parseInt(reader.nextLine());
		
		String[] line;
		//THe index Start from 1, will be used for each Figure's index
		for (int i = 1; i <= n; i++) {
			line = reader.nextLine().split(" ");
			
			figuri.add(new Figura(line[0], Integer.parseInt(line[1]), Integer.parseInt(line[2]), i));
			
		}
		
		reader.close();
	}
	
	//Read the input figures from already provided figures lines entered from the console
	protected void populateFiguriFromConsoleLines() throws FileNotFoundException {

		String[] line;
		for (int i = 0; i < this.consoleInputFigures.length; i++) {
			line = this.consoleInputFigures[i].split(" ");
			
			figuri.add(new Figura(line[0], Integer.parseInt(line[1]), Integer.parseInt(line[2]), i + 1));
		}
		
	}
	
	//For each figure update the field
	protected void initializeField() {

		for(Figura fig: figuri)
		{
			//Adding a Figure to the Field and updating each cell
			addToField(fig);
		}
	}
	
	//Fill the field with a Figure
	protected void addToField(Figura fig) {
		switch(fig.figuraType) {
			case"ninetile":
			{
				++banqta[fig.posX][fig.posY];
				++banqta[fig.posX][fig.posY-1];
				++banqta[fig.posX-1][fig.posY-1];
				++banqta[fig.posX-1][fig.posY];
				++banqta[fig.posX-1][fig.posY+1];
				++banqta[fig.posX][fig.posY+1];
				++banqta[fig.posX+1][fig.posY+1];
				++banqta[fig.posX+1][fig.posY];
				++banqta[fig.posX+1][fig.posY-1];
				break;
			}
			case"plus":
			{
				++banqta[fig.posX][fig.posY];
				++banqta[fig.posX][fig.posY-1];
				++banqta[fig.posX][fig.posY+1];
				++banqta[fig.posX-1][fig.posY];
				++banqta[fig.posX+1][fig.posY];
				break;
			}
			case"hline":
			{
				++banqta[fig.posX][fig.posY];
				++banqta[fig.posX][fig.posY-1];
				++banqta[fig.posX][fig.posY+1];
				break;
			}
			case"vline":
			{
				++banqta[fig.posX][fig.posY];
				++banqta[fig.posX-1][fig.posY];
				++banqta[fig.posX+1][fig.posY];
				break;
			}
			case"angle-ur":
			{
				++banqta[fig.posX][fig.posY];
				++banqta[fig.posX-1][fig.posY];
				++banqta[fig.posX][fig.posY+1];
				break;
			}
			case"angle-dr":
			{
				++banqta[fig.posX][fig.posY];
				++banqta[fig.posX+1][fig.posY];
				++banqta[fig.posX][fig.posY+1];
				break;
			}
			case"angle-ul":
			{
				++banqta[fig.posX][fig.posY];
				++banqta[fig.posX-1][fig.posY];
				++banqta[fig.posX][fig.posY-1];
				break;
			}
			case"angle-dl":
			{
				++banqta[fig.posX][fig.posY];
				++banqta[fig.posX+1][fig.posY];
				++banqta[fig.posX][fig.posY-1];
				break;
			}
		}
	}

	//Simpler Way
	//For each Figure if stuck, remove and place on a better spot
	protected void nalepiPlochki() {
		
		for(Figura fig : this.figuri) {
			//If a figure is stuck with another figure
			if (this.getStuckLevel(fig)>0) {
				//remove from field
				this.otlepiPlochka(fig);
				
				//find a new place for the Figure, the closest possible
				this.findBestPlaceForFigure(fig);
			}
		}
	}

	//Check a figure if stuck, and update the Field
	protected int getStuckLevel(Figura fig) {
		//clear previous stuck Level
		fig.stuckLevel = 0;
		
		switch(fig.figuraType) {
			case"ninetile":
			{
				if (banqta[fig.posX][fig.posY]>1) ++fig.stuckLevel;
				if (banqta[fig.posX][fig.posY-1]>1) ++fig.stuckLevel;
				if (banqta[fig.posX-1][fig.posY-1]>1) ++fig.stuckLevel;
				if (banqta[fig.posX-1][fig.posY]>1) ++fig.stuckLevel;
				if (banqta[fig.posX-1][fig.posY+1]>1) ++fig.stuckLevel;
				if (banqta[fig.posX][fig.posY+1]>1) ++fig.stuckLevel;
				if (banqta[fig.posX+1][fig.posY+1]>1) ++fig.stuckLevel;
				if (banqta[fig.posX+1][fig.posY]>1) ++fig.stuckLevel;
				if (banqta[fig.posX+1][fig.posY-1]>1) ++fig.stuckLevel;
				break;
			}
			case"plus":
			{
				if (banqta[fig.posX][fig.posY]>1) ++fig.stuckLevel;
				if (banqta[fig.posX][fig.posY-1]>1) ++fig.stuckLevel;
				if (banqta[fig.posX][fig.posY+1]>1) ++fig.stuckLevel;
				if (banqta[fig.posX-1][fig.posY]>1) ++fig.stuckLevel;
				if (banqta[fig.posX+1][fig.posY]>1) ++fig.stuckLevel;
				break;
			}
			case"hline":
			{
				if (banqta[fig.posX][fig.posY]>1) ++fig.stuckLevel;
				if (banqta[fig.posX][fig.posY-1]>1) ++fig.stuckLevel;
				if (banqta[fig.posX][fig.posY+1]>1) ++fig.stuckLevel;
				break;
			}
			case"vline":
			{
				if (banqta[fig.posX][fig.posY]>1) ++fig.stuckLevel;
				if (banqta[fig.posX-1][fig.posY]>1) ++fig.stuckLevel;
				if (banqta[fig.posX+1][fig.posY]>1) ++fig.stuckLevel;
				break;
			}
			case"angle-ur":
			{
				if (banqta[fig.posX][fig.posY]>1) ++fig.stuckLevel;
				if (banqta[fig.posX-1][fig.posY]>1) ++fig.stuckLevel;
				if (banqta[fig.posX][fig.posY+1]>1) ++fig.stuckLevel;
				break;
			}
			case"angle-dr":
			{
				if (banqta[fig.posX][fig.posY]>1) ++fig.stuckLevel;
				if (banqta[fig.posX+1][fig.posY]>1) ++fig.stuckLevel;
				if (banqta[fig.posX][fig.posY+1]>1) ++fig.stuckLevel;
				break;
			}
			case"angle-ul":
			{
				if (banqta[fig.posX][fig.posY]>1) ++fig.stuckLevel;
				if (banqta[fig.posX-1][fig.posY]>1) ++fig.stuckLevel;
				if (banqta[fig.posX][fig.posY-1]>1) ++fig.stuckLevel;
				break;
			}
			case"angle-dl":
			{
				if (banqta[fig.posX][fig.posY]>1) ++fig.stuckLevel;
				if (banqta[fig.posX+1][fig.posY]>1) ++fig.stuckLevel;
				if (banqta[fig.posX][fig.posY-1]>1) ++fig.stuckLevel;
				break;
			}
		}
		return fig.stuckLevel;
	}

	//Print the work done by the Worker
	protected void printWorkDone() {
		System.out.println("Work done by " + this.name + ":" + this.movesMade + " Time ->" + this.time);
	}
	
	//Print each figure, type, position
	private void printFigures() {
		for(Figura fig:figuri)
		{
			System.out.println(fig.toString());
		}
	}
	
	//Get the requested result
	public String getFinalResult() {
		StringBuilder result = new StringBuilder();
		
		for(Figura fig:figuri)
		{
			result.append(fig.posX).append(" ").append(fig.posY).append("\n");
		}
		
		return result.toString();
	}
	
	//Print the field to a file -> D:/MivkiResult.txt
	protected void printFieldToFile() throws IOException {
		BufferedWriter writer = new BufferedWriter(new FileWriter(new File(
				this.inputFile + "Result.txt")));
		
		for (int i = 0; i < this.banqta[0].length; i++) {
			for (int j = 0; j < this.banqta.length; j++) {
				if (this.banqta[i][j] == 0) writer.write("_");
				else writer.write('W');
			}
			writer.write("\n");
		}
		
		writer.close();
	}
}


